package org.springframework.issues;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.connection.JmsTransactionManager;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.persistence.*;
import javax.sql.DataSource;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* Unit test that reproduces an issue reported against SPR JIRA. @Test methods within
* need not pass with the green bar! Rather they should fail in such a way that
* demonstrates the reported issue.
*/
public class ReproTests {
@Test
public void jdbc() throws InterruptedException {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DataSourceContext.class)) {
TransactionalService service = ctx.getBean(TransactionalService.class);
{
service.execute("jdbc1", 0, 0); // OK
}
{
try {
service.execute("jdbc2", 2, 0); // OK
Assert.fail("This case should be aborted transaction");
} catch (TransactionException e) {
// NO-OP
}
}
{
try {
service.execute("jdbc3", 0, 2); // NG
Assert.fail("This case should be aborted transaction");
} catch (TransactionException e) {
// NO-OP
}
}
}
}
@Test
public void jpa() throws InterruptedException {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JpaContext.class)) {
TransactionalService service = ctx.getBean(TransactionalService.class);
{
service.execute("jpa1", 0, 0); // OK
}
{
try {
service.execute("jpa2", 3, 0); // OK
Assert.fail("This case should be aborted transaction");
} catch (TransactionException e) {
// NO-OP
}
}
{
try {
service.execute("jpa3", 0, 3); // NG
Assert.fail("This case should be aborted transaction");
} catch (TransactionException e) {
// NO-OP
}
}
}
}
@Test
public void jta() throws InterruptedException {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JtaDataSourceContext.class)) {
TransactionalService service = ctx.getBean(TransactionalService.class);
{
service.execute("jta1", 0, 0); // OK
}
{
try {
service.execute("jta2", 3, 0); // OK
Assert.fail("This case should be aborted transaction");
} catch (TransactionException e) {
// NO-OP
}
}
{
try {
service.execute("jta3", 0, 3); // OK
Assert.fail("This case should be aborted transaction");
} catch (TransactionException e) {
// NO-OP
}
}
}
}
@Test
public void jms() throws InterruptedException {
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JmsContext.class)) {
TransactionalService service = ctx.getBean(TransactionalService.class);
{
service.execute("jms1", 0, 0); // OK
}
{
try {
service.execute("jms2", 3, 0); // NG
Assert.fail("This case should be aborted transaction");
} catch (TransactionException e) {
// NO-OP
}
}
{
try {
service.execute("jms3", 0, 3); // NG
Assert.fail("This case should be aborted transaction");
} catch (TransactionException e) {
// NO-OP
}
}
}
}
@EnableTransactionManagement
@Configuration
static class DataSourceContext {
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
@Bean
DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
@Bean
TransactionalService service() {
return new TransactionalService((msg) -> System.out.println(jdbcTemplate().queryForObject("SELECT " + "'" + msg + "'", String.class)));
}
}
@EnableTransactionManagement
@Configuration
static class JpaContext {
@PersistenceContext
EntityManager entityManager;
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
@Bean
EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase(Database.H2);
adapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setJpaVendorAdapter(adapter);
factoryBean.setPackagesToScan("org.springframework.issues");
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
@Bean
JpaTransactionManager transactionManager() {
return new JpaTransactionManager(entityManagerFactory());
}
@Bean
TransactionalService service() {
return new TransactionalService((msg) -> System.out.println(entityManager.createQuery("SELECT x FROM ReproTests$Todo x WHERE x.id = 1", Todo.class).getResultList()));
}
}
@EnableTransactionManagement
@Configuration
static class JtaDataSourceContext {
@Bean(initMethod = "init", destroyMethod = "close")
DataSource dataSource() {
AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
dataSource.setXaDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
dataSource.setUniqueResourceName("dataSourceResource");
Properties props = new Properties();
props.setProperty("user", "sa");
props.setProperty("password", "");
props.setProperty("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
dataSource.setXaProperties(props);
return dataSource;
}
@Bean
JtaTransactionManager transactionManager() {
JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setTransactionManager(userTransactionManager());
return transactionManager;
}
@Bean(initMethod = "init", destroyMethod = "close")
UserTransactionManager userTransactionManager() {
UserTransactionManager transactionManager = new UserTransactionManager();
transactionManager.setForceShutdown(false);
return transactionManager;
}
@Bean
JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
@Bean
TransactionalService service() {
return new TransactionalService((msg) -> System.out.println(jdbcTemplate().queryForObject("SELECT " + "'" + msg + "'", String.class)));
}
}
@EnableJms
@EnableTransactionManagement
@Configuration
static class JmsContext {
@Bean
BrokerService brokerService() {
return new BrokerService();
}
@Bean
ActiveMQConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory("vm://localhost");
}
@Bean
JmsTransactionManager transactionManager() {
return new JmsTransactionManager(connectionFactory());
}
@Bean
JmsTemplate jmsTemplate() {
return new JmsTemplate(connectionFactory());
}
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
return factory;
}
@Bean
TransactionalService service() {
return new TransactionalService((msg) -> jmsTemplate().send("testQueue", (s) -> s.createTextMessage(msg)));
}
}
@Transactional(timeout = 2)
@Service
static class TransactionalService {
private final Consumer<String> consumer;
public TransactionalService(Consumer<String> consumer) {
this.consumer = consumer;
}
public void execute(String msg, int beforeWaitSec, int afterWaitSec) throws InterruptedException {
TimeUnit.SECONDS.sleep(beforeWaitSec);
consumer.accept(msg);
TimeUnit.SECONDS.sleep(afterWaitSec);
}
@JmsListener(destination = "testQueue")
public void print(String msg) {
System.out.println(msg);
}
}
@Entity
static class Todo {
@Id
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
}